﻿using System;
using System.Net;
using System.Net.Sockets;
using iControl;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using System.Collections.Specialized;
using System.Configuration;


namespace F5LoadBalancer {
    public static class Utility {
        private const int MachinePort = 80;
        public static void SetPoolMemberState(string poolName, string machineName, CommonEnabledState newState) {
            CommonIPPortDefinition ipPort = GetPortDefinitionFromMachineName(machineName);
            LocalLBPoolMemberMemberSessionState[][] sessionStates = new LocalLBPoolMemberMemberSessionState[1][];
            sessionStates[0] = new LocalLBPoolMemberMemberSessionState[1];
            sessionStates[0][0] = new LocalLBPoolMemberMemberSessionState();
            sessionStates[0][0].member = ipPort;
            sessionStates[0][0].session_state = newState;
            m_interfaces.LocalLBPoolMember.set_session_enabled_state(new string[] { poolName }, sessionStates);
        }

        private static bool ExistsPool(string pool) {
            string[] pools = m_interfaces.LocalLBPool.get_list();
            foreach (string loopPool in pools) {
                if (string.Equals(pool, loopPool, StringComparison.OrdinalIgnoreCase)) {
                    return true;
                }
            }

            return false;
        }

        private static bool ExistsPoolMember(string pool, string member) {
            
            CommonIPPortDefinition[][] members = m_interfaces.LocalLBPool.get_member(new string[] { pool });
            string machineIpAddress = GetIpAddress(member);
            for (int i = 0; i < members[0].Length; i++) {
                if (string.Equals(members[0][i].address, machineIpAddress, StringComparison.OrdinalIgnoreCase)) {
                    return true;
                }
            }

            return false;
        }

        public static void CreatePool(string pool) {
            CreateEventLog();
            
            if (ExistsPool(pool)) {
                WriteEntry("Pool already exists:" + pool);
                return;
            }

            CommonIPPortDefinition[][] definitions = new CommonIPPortDefinition[1][];
            definitions[0] = new CommonIPPortDefinition[0];
            m_interfaces.LocalLBPool.create(new string[] { pool }, new LocalLBLBMethod[] { LocalLBLBMethod.LB_METHOD_ROUND_ROBIN }, definitions);
            WriteEntry("Pool " + pool + " created");
            SetMonitorAssociaton(pool);
        }

        private static void SetMonitorAssociaton(string pool) {
            LocalLBPoolMonitorAssociation association = new LocalLBPoolMonitorAssociation();
            association.pool_name = pool;
            association.monitor_rule = new LocalLBMonitorRule();
            association.monitor_rule.type = LocalLBMonitorRuleType.MONITOR_RULE_TYPE_AND_LIST;
            association.monitor_rule.quorum = 0;
            association.monitor_rule.monitor_templates = new string[] { "tcp" };
            m_interfaces.LocalLBPool.set_monitor_association(new LocalLBPoolMonitorAssociation[] { association });
        }

        public static void RemovePool(string pool) {
            if (!ExistsPool(pool)) {
                return;
            }
            m_interfaces.LocalLBPool.delete_pool(new string[] { pool });
        }

        public static void CreatePoolMember(string poolName, string machineName, CommonEnabledState initialState) {
            if (!ExistsPoolMember(poolName, machineName)) {
                m_interfaces.LocalLBPool.add_member(new string[] { poolName }, GetDefinition(machineName));
            }

            SetPoolMemberState(poolName, machineName, initialState);
        }
        public static void WriteEntry(string entry)
        {
            EventLog.WriteEntry("WFF", entry);
        }

        public static void CreateEventLog()
        {

            if (!EventLog.SourceExists("WFF"))
            {
                EventLog.CreateEventSource("WFF", "Web Farm Framework");
                WriteEntry("WFF source and logname are created");
            }
           
        }
        private static CommonIPPortDefinition GetPortDefinitionFromMachineName(string machineName) {
            CommonIPPortDefinition ipPort = new CommonIPPortDefinition();
            ipPort.address = GetIpAddress(machineName);
            ipPort.port = MachinePort;
            return ipPort;
        }

        public static void RemovePoolMember(string pool, string member) {
            if (!ExistsPoolMember(pool, member)) {
                return;
            }

            m_interfaces.LocalLBPool.remove_member(new string[] { pool }, GetDefinition(member));
        }

        private static CommonIPPortDefinition[][] GetDefinition(string machineName) {
            CommonIPPortDefinition[][] definitions = new CommonIPPortDefinition[1][];
            definitions[0] = new CommonIPPortDefinition[1];
            definitions[0][0] = new CommonIPPortDefinition();
            definitions[0][0].address = GetIpAddress(machineName);
            definitions[0][0].port = MachinePort;
            return definitions;
        }

        private static string GetIpAddress(string machineName) {
            if (IsIpAddress(machineName)) {
                return machineName;
            }

            IPHostEntry entry = Dns.GetHostEntry(machineName);
            foreach (IPAddress item in entry.AddressList) {
                if (item.AddressFamily == AddressFamily.InterNetwork) {
                    return item.ToString();
                }
            }

            return null;
        }

        private static bool IsIpAddress(string ipAddress) {
            try {
                IPAddress.Parse(ipAddress);
                return true;
            }
            catch (FormatException) {
                return false;
            }
        }

        private static Interfaces m_interfaces = new Interfaces(F5Constants.Url, F5Constants.F5UserName, F5Constants.F5Password);
    }

    internal static class F5Constants {
        private static string _url = string.Empty;
        private static string configFileName = string.Empty;
        private static Configuration config = null;

        public static Configuration Config
        {
            get {
                CheckConfigFileExists();
                return  F5Constants.config;
            }
            set { F5Constants.config = value; }
        }
        public static void CheckConfigFileExists()
        {
            
            if (config == null)
            {
                Utility.CreateEventLog();
                System.Reflection.Assembly asm = Assembly.GetExecutingAssembly();
                ExeConfigurationFileMap map = new ExeConfigurationFileMap();
                map.ExeConfigFilename = asm.Location + ".config";
                Utility.WriteEntry("Executing Assembly Path is " + asm.Location);
                config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);
                Utility.WriteEntry("F5 Urls is " + Config.AppSettings.Settings["F5_IP"].Value.ToString());
            }

        }

            
        public static string Url
        {
            get {
                CheckConfigFileExists();
                if (string.IsNullOrEmpty(_url))
                {

                    _url = Config.AppSettings.Settings["F5_IP"].Value.ToString();
                    if (string.IsNullOrEmpty(_url)) throw new Exception ("Can not read F5_IP parameter");
                }
                return _url; }
        }

        private static string AdminUserName = string.Empty;

        public static string F5UserName
        {
            get {

                if (string.IsNullOrEmpty(AdminUserName))
                {
                    AdminUserName = Config.AppSettings.Settings["F5_USERNAME"].Value.ToString();
                    if (string.IsNullOrEmpty(AdminUserName)) throw new Exception("Can not read F5 Admin username");
                }
                return AdminUserName; }
        }

        private static string AdminPassword = string.Empty ;

        public static string F5Password
        {
            get {
                if (string.IsNullOrEmpty(AdminPassword))
                {
                   AdminPassword = Config.AppSettings.Settings["F5_PASSWORD"].Value.ToString();
                    if (string.IsNullOrEmpty(AdminPassword)) throw new Exception ("Can not read F5 Password");
                }
                return AdminPassword; }
        } 

        public const string F5LoadBalancerName = "F5";
    }
}
